"use strict";
var __assign = (this && this.__assign) || function () {
    __assign = Object.assign || function(t) {
        for (var s, i = 1, n = arguments.length; i < n; i++) {
            s = arguments[i];
            for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
                t[p] = s[p];
        }
        return t;
    };
    return __assign.apply(this, arguments);
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __generator = (this && this.__generator) || function (thisArg, body) {
    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
    return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
    function verb(n) { return function (v) { return step([n, v]); }; }
    function step(op) {
        if (f) throw new TypeError("Generator is already executing.");
        while (g && (g = 0, op[0] && (_ = 0)), _) try {
            if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
            if (y = 0, t) op = [op[0] & 2, t.value];
            switch (op[0]) {
                case 0: case 1: t = op; break;
                case 4: _.label++; return { value: op[1], done: false };
                case 5: _.label++; y = op[1]; op = [0]; continue;
                case 7: op = _.ops.pop(); _.trys.pop(); continue;
                default:
                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
                    if (t[2]) _.ops.pop();
                    _.trys.pop(); continue;
            }
            op = body.call(thisArg, _);
        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
    }
};
exports.__esModule = true;
var net = require('net');
var io = require('socket.io-client');
var colors = require('colors/safe');
var crypto_1 = require("crypto");
var data_c_id = 0;
var encryptList = ['aes-128-cbc', 'aes-192-cbc', 'aes-256-cbc'];
var ClientServer = /** @class */ (function () {
    function ClientServer(argv) {
        this.key = "";
        this.iv = "";
        /*
         *  example:
            structure:
          {
          wall: {
            local_port: 8080,
            remote_port: 10004
          },
          www: {
            local_port: 8090,
            remote_port: 9080
          },//本地服务名=>端口映射
        }
         */
        this.binds = {};
        this.isBinded = false;
        this.isConnected = false;
        this.isRunning = false;
        this.mainsSocket = {};
        this.localConnectingSockets = {};
        this.reconnetInterval = -1;
        this.logger = {
            info: console.log
        };
        this.encryptMethod = argv.encrypt;
        if (encryptList.includes(this.encryptMethod)) {
            this.key = argv.key;
            this.iv = argv.iv;
        }
        this.url = argv.url;
        this.persistentConnectionUID = argv.unid;
        if (!this.url) {
            console.log(colors.red('Error: Could not get url'));
        }
        // if (argv.binds) {
        //   this.binds = argv.binds
        // }
    }
    ClientServer.prototype.init = function () {
        var _this = this;
        this.localConnectingSockets = {};
        this.mainsSocket = io(this.url);
        console.log("connect to ".concat(this.url));
        // socket.on()用于接收服务端发来的消息
        this.mainsSocket.on('connect', function () {
            clearInterval(_this.reconnetInterval);
            _this.reconnetInterval = -1;
            _this.isConnected = true;
            console.log('client connect server');
        });
        this.mainsSocket.on('base config', function (res) {
            var clients = res.clients;
            Object.keys(clients[_this.persistentConnectionUID].binds).forEach(function (key) {
                _this.binds[_this.persistentConnectionUID + key] =
                    clients[_this.persistentConnectionUID].binds[key];
            });
            _this.mainsSocket.emit('persistentConnectionUID', _this.encrypt(_this.persistentConnectionUID));
            console.log(_this.binds);
            var _loop_1 = function (serverKey) {
                console.log(serverKey);
                _this.mainsSocket.on(serverKey, function (d) { return __awaiter(_this, void 0, void 0, function () {
                    return __generator(this, function (_a) {
                        switch (_a.label) {
                            case 0:
                                console.log(d);
                                return [4 /*yield*/, this.handleDataFromFrontServer(serverKey, d)];
                            case 1:
                                _a.sent();
                                return [2 /*return*/];
                        }
                    });
                }); });
            };
            for (var serverKey in _this.binds) {
                _loop_1(serverKey);
            }
        });
        this.mainsSocket.on('disconnect', function (reason) {
            return;
            console.log("\u957F\u8FDE\u65AD\u5F00\uFF0C\u539F\u56E0: ".concat(reason));
            console.log('处理disconnect事件');
            _this.isConnected = false;
            try {
                console.log('重连');
                _this.reconnetInterval = setInterval(function () {
                    if (!_this.isConnected) {
                        _this.mainsSocket.close();
                        _this.clear();
                        _this.init();
                    }
                }, 10 * 1000);
            }
            catch (err) {
                console.error(err);
            }
        });
        this.mainsSocket.on('ping', function () {
            console.log('[E] 心跳请求已发出 →');
        });
        //收到
        this.mainsSocket.on('pong', function () {
            console.log('[E] 心跳响应已收到 ←');
        });
    };
    // 关闭与源服务的连接
    ClientServer.prototype.encrypt = function (data) {
        if (this.key) {
            var cipher = crypto_1["default"].createCipheriv(this.encryptMethod, this.key, this.iv);
            var encrypted = cipher.update(JSON.stringify(data), 'utf8', 'hex');
            encrypted += cipher.final('hex');
            return encrypted;
        }
        return data;
    };
    ClientServer.prototype.encryptBuffer = function (buf) {
        if (this.key) {
            var cipher = crypto_1["default"].createCipheriv(this.encryptMethod, this.key, this.iv);
            return Buffer.concat([cipher.update(buf), cipher.final()]);
        }
        return buf;
    };
    ClientServer.prototype.decryptBuffer = function (buf) {
        if (this.key) {
            var decipher = crypto_1["default"].createDecipheriv(this.encryptMethod, this.key, this.iv);
            return Buffer.concat([decipher.update(buf), decipher.final()]);
        }
        return buf;
    };
    ClientServer.prototype.clear = function () {
        for (var key in this.localConnectingSockets) {
            try {
                this.localConnectingSockets[key].end();
                this.localConnectingSockets[key] = null;
                delete this.localConnectingSockets[key];
            }
            catch (err) {
                console.error(err);
            }
        }
    };
    // 向指定ip和端口的服务源发送请求数据
    ClientServer.prototype.createLocalConnectingSocket = function (serverKey, requestKey) {
        return __awaiter(this, void 0, void 0, function () {
            var that;
            var _this = this;
            return __generator(this, function (_a) {
                that = this;
                return [2 /*return*/, new Promise(function (resolve, reject) {
                        _this.localConnectingSockets[requestKey] = net.connect({
                            host: _this.binds[serverKey].local_host,
                            port: _this.binds[serverKey].local_port
                        }, function () {
                            console.log('监听端口: ', _this.binds[serverKey].local_port);
                            resolve(true);
                        });
                        _this.localConnectingSockets[requestKey].on('data', function (data) {
                            console.log("send s data: ".concat(Buffer.from(data).toString('utf-8', 0, 100)));
                            try {
                                that.mainsSocket.emit(serverKey, {
                                    cmd: 'data',
                                    serverKey: serverKey,
                                    requestKey: requestKey,
                                    data: _this.encryptBuffer(data),
                                    data_c_id: data_c_id++
                                });
                            }
                            catch (err) {
                                console.error(err);
                            }
                        });
                        _this.localConnectingSockets[requestKey].on('error', function (err) {
                            console.error(err);
                            that.handleWhenErrorOrClose(serverKey, requestKey);
                            reject(err);
                        });
                        _this.localConnectingSockets[requestKey].on('close', function () {
                            that.handleWhenErrorOrClose(serverKey, requestKey);
                            reject();
                        });
                        var tmp = setTimeout(function () {
                            clearTimeout(tmp);
                            reject();
                        }, 10 * 1000);
                    })];
            });
        });
    };
    // 处理错误或异常关闭
    ClientServer.prototype.handleWhenErrorOrClose = function (serverKey, requestKey) {
        if (!this.localConnectingSockets[requestKey]) {
            return;
        }
        this.localConnectingSockets[requestKey].end();
        this.localConnectingSockets[requestKey] = null;
        delete this.localConnectingSockets[requestKey];
        this.mainsSocket.emit(serverKey, { cmd: 'close', requestKey: requestKey });
    };
    ClientServer.prototype.handleDataFromFrontServer = function (serverKey, d) {
        return __awaiter(this, void 0, void 0, function () {
            var cmd, requestKey, data, decrypted, res, err_1;
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        cmd = d.cmd, requestKey = d.requestKey, data = d.data;
                        if (!(cmd === 'data')) return [3 /*break*/, 6];
                        decrypted = this.decryptBuffer(data);
                        console.log("receive s data: ".concat(Buffer.from(decrypted).toString('utf-8', 0, 100)));
                        _a.label = 1;
                    case 1:
                        _a.trys.push([1, 4, , 5]);
                        res = true;
                        if (!!this.localConnectingSockets[requestKey]) return [3 /*break*/, 3];
                        console.log('创建本地转发连接');
                        return [4 /*yield*/, this.createLocalConnectingSocket(serverKey, requestKey)];
                    case 2:
                        res = _a.sent();
                        _a.label = 3;
                    case 3:
                        console.log('向本地连接发送数据', decrypted);
                        if (res === true && this.localConnectingSockets[requestKey]) {
                            this.localConnectingSockets[requestKey].write(decrypted);
                        }
                        return [3 /*break*/, 5];
                    case 4:
                        err_1 = _a.sent();
                        console.error('error from catch', err_1);
                        this.localConnectingSockets[requestKey].end();
                        this.localConnectingSockets[requestKey] = null;
                        delete this.localConnectingSockets[requestKey];
                        this.handleWhenErrorOrClose(serverKey, requestKey);
                        return [3 /*break*/, 5];
                    case 5: return [3 /*break*/, 7];
                    case 6:
                        if (cmd === 'close') {
                            console.log("close requestKey: ".concat(requestKey));
                            if (!this.localConnectingSockets[requestKey]) {
                                return [2 /*return*/];
                            }
                            this.localConnectingSockets[requestKey].end();
                            this.localConnectingSockets[requestKey] = null;
                            delete this.localConnectingSockets[requestKey];
                        }
                        _a.label = 7;
                    case 7: return [2 /*return*/];
                }
            });
        });
    };
    /*
     * binds: ex:['wall', 'www']
     */
    ClientServer.prototype.removeBinds = function (binds) {
        this.mainsSocket.emit('removeBinds', this.encrypt(binds));
    };
    /*
     {
      wall: {
        local_port: 8080,
        remote_port: 10004
      }
     */
    ClientServer.prototype.addBinds = function (binds) {
        var _this = this;
        var remote_ports = [];
        for (var key in binds) {
            // 如果用户没有设置服务源ip,则把ip设置为127.0.0.1
            if (!binds[key].remote_ip) {
                binds[key].remote_ip = '127.0.0.1';
            }
            remote_ports.push(binds[key].remote_port);
            if (this.binds[key]) {
                throw "Error: the bind of '".concat(key, "' existed");
            }
        }
        for (var key in this.binds) {
            if (remote_ports.includes(this.binds[key].remote_port)) {
                throw "Error: the remote port of '".concat(this.binds[key].remote_port, "' existed");
            }
        }
        console.log("before, binds: ".concat(JSON.stringify(this.binds)));
        this.binds = __assign(__assign({}, this.binds), binds);
        console.log("after, binds: ".concat(JSON.stringify(this.binds)));
        this.mainsSocket.emit('addBinds', this.encrypt(binds));
        var _loop_2 = function (serverKey) {
            this_1.mainsSocket.on(serverKey, function (d) { return __awaiter(_this, void 0, void 0, function () {
                return __generator(this, function (_a) {
                    switch (_a.label) {
                        case 0: return [4 /*yield*/, this.handleDataFromFrontServer(serverKey, d)];
                        case 1:
                            _a.sent();
                            return [2 /*return*/];
                    }
                });
            }); });
        };
        var this_1 = this;
        for (var serverKey in binds) {
            _loop_2(serverKey);
        }
    };
    ClientServer.prototype.start = function () {
        this.init();
    };
    return ClientServer;
}());
module.exports = { ClientServer: ClientServer };
